/*
 *  Ship.cpp
 * 
 *
 *  Created by Alan Dorin on 12/04/07.
 *  Copyright 2007 __MyCompanyName__. All rights reserved.
 *
 */
 
using namespace std;


#include "ShipBase.h"

#include "Sea.h"
#include "AIController.h"
#include "Globals.h"

#include <fstream>

extern Sea* gSea;
extern Globals gGlobal;
extern void cleanQuit(char*);

// private methods...

void Ship::setJustDamagedFlag(void)		// this flag is set automatically when the damage is applied in applyDamage()
{ justDamagedFlag=true; }
		
// pubic methods...

Ship::Ship(void)
{	
	position[vX] = position[vY] = position[vZ] = -1;
	uniqueId = -1;
	colour.set(0.7);
	
	rewardStored = 0;
	
	damage		= 0;
	maxDamage	= 10;
	cannonRange = 4;
	
	aheadDirection = Globals::neighbourUp;
	requestedAction = Globals::actionPass;
	requestedActionSuccessFlag = false;
	
	controller = NULL;
}

Ship::Ship(const Ship& src)
{
	position[vX] = src.position[vX];
	position[vY] = src.position[vY];
	position[vZ] = src.position[vZ];
	
	uniqueId	= src.uniqueId;
	colour		= src.colour;
	
	rewardStored = src.rewardStored;
	
	damage		= src.damage;
	maxDamage	= src.maxDamage;
	cannonRange = src.cannonRange;
	
	aheadDirection = src.aheadDirection;
	requestedAction = src.requestedAction;
	requestedActionSuccessFlag = src.requestedActionSuccessFlag;
	
	// Copy the *pointer* to the controller across to the new ship
	// so that both ships (e.g. future ship and preset ship) share the same
	// controller and all its internal state. If I had separate controller objects
	// for each version of a ship, the students would need to write a copy
	// constructor for their own controllers... I am trying to avoid this.
	controller = src.controller;
}

Ship::~Ship()
{
	// do NOT delete the controller.
	// these are made outside of Ship and deleted from outside of ship
}

void Ship::setControllerPtr(AIController* newController)
{	controller = newController;	}

void Ship::setPosition(const long x, const long y, const long z)
{ position[vX] = x;	position[vY] = y; position[vZ] = z; }

long Ship::getPositionX(void)
const
{ return position[vX]; }

long Ship::getPositionY(void)
const
{ return position[vY]; }

long Ship::getPositionZ(void)
const
{ return position[vZ]; }

Globals::NeighbourDirection Ship::getAheadDirection(void)
const
{ return aheadDirection; }

void Ship::setAheadDirection(Globals::NeighbourDirection newAheadDirection)
{	aheadDirection = newAheadDirection;	}
		
long Ship::getCannonRange(void)
const
{	return cannonRange; }

void Ship::applyDamage(long damageSustained)
{	damage+=damageSustained;
	setJustDamagedFlag();
}

long Ship::getDamage(void)
const
{ return damage; }

long Ship::getMaxDamage(void)
const
{ return maxDamage; }

void Ship::clearJustDamagedFlag(void)
{ justDamagedFlag=false; }

bool Ship::justDamaged(void)
const
{ return justDamagedFlag; }

bool Ship::isSunk(void)
const
{	return ((damage >= maxDamage) ? true : false);	}

long Ship::getRewardStored(void)
const
{ return rewardStored; }

void Ship::addToRewardStored(long extraReward)
{ rewardStored += extraReward; }

void Ship::clearRewardStored(void)
{ rewardStored = 0; }

void Ship::setRequestedAction(Globals::ShipAction newAction)
{ requestedAction = newAction; }

Globals::ShipAction Ship::getRequestedAction(void)
const
{ return requestedAction; }

void Ship::setRequestedActionSuccessFlag(bool newFlag)
{ requestedActionSuccessFlag = newFlag; }

bool Ship::getRequestedActionSuccessFlag(void)
const
{ return requestedActionSuccessFlag; }
		

// *NB this method computes the next action the ship wants to perform
// but it does not change the ship's state. The ship's request for this
// action will be assessed by the "Sea" framework and success or failure will be returned.

Globals::ShipAction Ship::computeNextAction(void)
{	
	assert (controller!=NULL);
	
	// clear the flag for this ship's action's success
	requestedActionSuccessFlag = false;
	
	Globals::ShipAction action = controller->computeNextAction();
	return action;
}

bool Ship::checkMoveDirection(Globals::NeighbourDirection moveDirection)
{
	extern long getAdjacentIndexX(Globals::NeighbourDirection direction, long curIndex, long xDimension);
	extern long getAdjacentIndexY(Globals::NeighbourDirection direction, long curIndex, long yDimension);
	
	long cellArrayDimension = gSea->getCellArrayDimension();
	long xNewIndex, yNewIndex;
		
	xNewIndex = getAdjacentIndexX(moveDirection, getPositionX(), cellArrayDimension);
	yNewIndex = getAdjacentIndexY(moveDirection, getPositionY(), cellArrayDimension);

	// test in the cellArrayBuffer (the *future* time array) if the cell it will move to is unoccupied by ships or rocks
	if ((gSea->cellArrayBufferIsOccupiedByShip(xNewIndex, yNewIndex)) || (gSea->cellArrayBufferIsOccupiedByRock(xNewIndex, yNewIndex)) )
	{	return false; }
	
	return true; // if we get all the way to the last atom then there are no collisions
}

void Ship::makeMove(Globals::NeighbourDirection moveDirection)
{
	extern long getAdjacentIndexX(Globals::NeighbourDirection direction, long curIndex, long xDimension);
	extern long getAdjacentIndexY(Globals::NeighbourDirection direction, long curIndex, long yDimension);
	
	double cellArrayDimension = gSea->getCellArrayDimension();
	long xNewIndex, yNewIndex;
		
	xNewIndex = getAdjacentIndexX(moveDirection, getPositionX(), cellArrayDimension);
	yNewIndex = getAdjacentIndexY(moveDirection, getPositionY(), cellArrayDimension);

	// this test should already have been done in checkMoveDirection() but here's a back-up to avoid trouble...
	// test in the cellArrayBuffer (the *future* time array) if the cell it will move to is available
	if (gSea->cellArrayBufferIsOccupiedByShip(xNewIndex, yNewIndex))
	{	cleanQuit("ERROR (Ship::makeMove): attempt to move a ship into the cellArrayBuffer at an occupied location."); }

	// make the move (this sets the ship's position in the *buffer*)
	// std::cerr << "\n(Ship::makeMove) Id=" << getId() << " curX=" << getPositionX() << " curY=" << getPositionY();
	// std::cerr << "\n(Ship::makeMove) Id=" << getId() << " newX=" << xNewIndex << " newY=" << yNewIndex;
	
	gSea->setShipPositionBuffer(getId(), xNewIndex, yNewIndex);
	gSea->placeBufferShipInBufferCellArray(getId());
}

bool Ship::checkStationary()
{
	//std::cerr << "\nWARN (Ship::checkStationary)";
	
	// test in the cellArrayBuffer (the *future* time array) if the cell it is currently in remains available
	if (gSea->cellArrayBufferIsOccupiedByShip(getPositionX(), getPositionY()))
	{	return false; }

	return true;
}

void Ship::makeStationary()
{	
	// this test should already have been done in checkStationary() but here's a back-up to avoid trouble...
	// test in the cellArrayBuffer (the *future* time array) if the cell it will stay in is available
	// but ONLY do this test for unsunken ships (sunken ships don't care if another unsunken or sunken ship is present)
	
	if (gSea->cellArrayBufferIsOccupiedByShip(getPositionX(), getPositionY()))
	{	cleanQuit("ERROR (Ship::makeStationary): attempt to leave a ship in the cellArrayBuffer at an occupied location."); }
	
	// make stationary (this sets the ship's position in the *buffer*)
	// std::cerr << "\n(Ship::makeStationary) shipId=" << getId() << " x=" << getPositionX() << " y=" << getPositionY() << " ahead=" << aheadDirection;
	gSea->setShipPositionBuffer(getId(), getPositionX(), getPositionY());
	gSea->placeBufferShipInBufferCellArray(getId());
}

long Ship::getId(void)
const
{ return uniqueId; }

void Ship::setId(long newId)
{uniqueId = newId; }

void Ship::debugOutput(ostream& outStream)
{
	outStream << *this;
}

void Ship::display(const SimpleVector& cellPosition, const SimpleVector& cellDimensions)
{
	long frontVertexIndex=0;
	SimpleVector green(0,0.9,0), red(0.5,0,0), black(0,0,0), white(1.0,1.0,1.0);
	SimpleVector grey(0.2,0.2,0.2), yellow(1.0,1.0,0.0), blue(0.0,0.0,0.7), brown(0.7,0.3,0);
	SimpleVector insetDistance = cellDimensions/5.0;
	SimpleVector textLeftEdge(cellPosition.v[vX] - insetDistance.v[vX], cellPosition.v[vY] + insetDistance.v[vY], cellPosition.v[vZ]);
	SimpleVector bowLocation(cellDimensions.v[vX], 0, 0); // default is +x
	extern void writeTextString(const SimpleVector& rasterPosition, char* nameString);
	extern void drawWireSquare(const SimpleVector& position, const SimpleVector& dimensions);
	extern void drawFillSquare(const SimpleVector& position, const SimpleVector& dimensions);
	extern void drawMark(const SimpleVector& position);
	extern void drawFillTriangle(const SimpleVector& position, const SimpleVector& dimensions, long frontVertexIndex);
	extern void drawWireTriangle(const SimpleVector& position, const SimpleVector& dimensions, long frontVertexIndex);
	
	if (!isSunk())
	{
		switch (requestedAction)
		{
			case Globals::actionMoveAhead:
			case Globals::actionTurnLeft:
			case Globals::actionTurnRight:
			case Globals::actionFireCannonAhead:
			case Globals::actionPass:
				brown.vector_gl_colour();
			break;
			
			case Globals::actionCollectGold:
				yellow.vector_gl_colour();
			break;
			
			default:
				cleanQuit("ERROR (Ship::display()): fallen off switch for actionRequested!");
		}
		
		if (justDamaged() == true)	// overide all other colours, just draw red
		{	red.vector_gl_colour();	}
		
		switch (getAheadDirection())
		{
			case Globals::neighbourUp:
				bowLocation.set(0,cellDimensions.v[vY]/2.0,0);
				frontVertexIndex = 0;
			break;
			case Globals::neighbourRight:
				bowLocation.set(cellDimensions.v[vX]/2.0,0,0);
				frontVertexIndex = 1;
			break;
			case Globals::neighbourDown:
				bowLocation.set(0,-cellDimensions.v[vY]/2.0,0);
				frontVertexIndex = 2;
			break;
			case Globals::neighbourLeft:
				bowLocation.set(-cellDimensions.v[vX]/2.0,0,0);
				frontVertexIndex = 3;
			break;
			
			default:
				cleanQuit("ERROR (Ship::display()): fallen off switch for aheadDirection!");
		}
		
		drawFillTriangle(cellPosition, cellDimensions-insetDistance, frontVertexIndex);
		
		// Draw the cannon (grey if !firing, green if firing)
		if (requestedAction == Globals::actionFireCannonAhead)
		{	green.vector_gl_colour();	}
		
		else
		{	grey.vector_gl_colour();	}
		
		drawMark(cellPosition+bowLocation);
		
		// Show a failed attempt at an action by drawing a red triangle around the ship
		red.vector_gl_colour();
		if (requestedActionSuccessFlag==false)
		{
			drawWireTriangle(cellPosition, cellDimensions-insetDistance, frontVertexIndex);
		}
	}
	
	else	// on the step when they are first sunk (the last time the ship will be drawn in its entirety)
			// draw a filled blue square
	{
		blue.vector_gl_colour();
		drawFillSquare(cellPosition, cellDimensions-insetDistance);
	}
	
	// write the ship -- id, stored gold amount and damage
	white.vector_gl_colour(); 
	char theName[30];
	sprintf(theName, "#%ld $%ld dmg%ld", uniqueId, rewardStored, damage);
	writeTextString(textLeftEdge, theName);
}

Ship& Ship::operator= (const Ship& rhs)
{	
	position[vX] = rhs.position[vX];
	position[vY] = rhs.position[vY];
	position[vZ] = rhs.position[vZ];
	
	colour		= rhs.colour;
	uniqueId	= rhs.uniqueId;
	
	rewardStored = rhs.rewardStored;
	
	damage		= rhs.damage;
	maxDamage	= rhs.maxDamage;
	cannonRange = rhs.cannonRange;
	
	aheadDirection = rhs.aheadDirection;
	requestedAction = rhs.requestedAction;
	requestedActionSuccessFlag = rhs.requestedActionSuccessFlag;
	
	controller = rhs.controller;
	
	return *this;
}

void Ship::fileInput(ifstream& inFile)
{
	inFile >> position[0] >> position[1] >> position[2]; // a set of grid indices (not continuous positions)
	inFile >> colour; // read in a vector

	inFile >> uniqueId;
}

void Ship::fileOutput(ofstream& outFile)
{
	outFile << position[0] << " " << position[1] << " " << position[2];
	outFile << colour; // write out a vector

	outFile << uniqueId << endl;
}

void Ship::outputStatistics(long curFrame)
{
	cerr << "\n#" << uniqueId << ",";
	cerr << " $" << rewardStored << ",";
	cerr << " !" << damage << "/" << maxDamage;
	
	if (isSunk())
	{	cerr <<	" --- sunk"; }
}

// ---------------------------------------------------------------------------------------

ostream& operator<< (ostream& outFile, const Ship& theShip)
{
	outFile << "\nstart ship...";
	outFile << "\nend ship";
	return outFile;
}
